﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  [Pure]
  public static class ArrayExtensions
  {
    public static IList<T> AsList<T>(this T[] array) {
      Argument.NotNull(array, "array");
      Contract.Ensures(Contract.Result<IList<T>>() != null);
      return array ?? Sequence.GetEmptyArray<T>();
    }

    public static IReadOnlyList<T> AsReadOnlyList<T>(this T[] array) {
      Argument.NotNull(array, "array");
      Contract.Ensures(Contract.Result<IReadOnlyList<T>>() != null);
      return array ?? Sequence.GetEmptyArray<T>();
    }

    #region ForEach

    public static void ForEach<T>(this T[] array, Action<T> action) {
      Argument.NotNull(array, "array");
      Argument.NotNull(action, "action");
      Array.ForEach(array, action);
    }

    public static void ForEach<T>(this T[] array, Action<T, int> action) {
      Argument.NotNull(array, "array");
      Argument.NotNull(action, "action");
      for(var index = 0; index < array.Length; index++) {
        action(array[index], index);
      }//for
    }

    #endregion ForEach

    #region BinarySearch

    public static int BinarySearch<T>(this T[] array, T value) {
      Argument.NotNull(array, "array");
      return Array.BinarySearch(array, value);
    }

    public static int BinarySearch<T>(this T[] array, T value, IComparer<T> comparer) {
      Argument.NotNull(array, "array");
      return Array.BinarySearch(array, value, comparer);
    }

    public static int BinarySearch<T>(this T[] array, int index, int length, T value) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(index >= 0, index, "index");
      Argument.OutOfRange(index <= array.Length - length, index, "index");
      Argument.OutOfRange(length >= 0, length, "length");
      Argument.OutOfRange(length <= array.Length, length, "length");
      return Array.BinarySearch(array, index, length, value);
    }

    public static int BinarySearch<T>(this T[] array, int index, int length, T value, IComparer<T> comparer) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(index >= 0, index, "index");
      Argument.OutOfRange(index <= array.Length - length, index, "index");
      Argument.OutOfRange(length >= 0, length, "length");
      Argument.OutOfRange(length <= array.Length, length, "length");
      return Array.BinarySearch(array, index, length, value, comparer);
    }

    #endregion BinarySearch

    public static TOutput[] ConvertAll<TInput, TOutput>(this TInput[] array, Converter<TInput, TOutput> converter) {
      Argument.NotNull(array, "array");
      Argument.NotNull(converter, "converter");
      return Array.ConvertAll(array, converter);
    }

    public static bool Exists<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.Exists(array, match);
    }

    public static T Find<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.Find(array, match);
    }

    public static T[] FindAll<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.FindAll(array, match);
    }

    #region FindIndex

    public static int FindIndex<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.FindIndex(array, match);
    }

    public static int FindIndex<T>(this T[] array, int startIndex, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Argument.OutOfRange(startIndex < array.Length, startIndex, "startIndex");
      Argument.NotNull(match, "match");
      return Array.FindIndex(array, startIndex, match);
    }

    public static int FindIndex<T>(this T[] array, int startIndex, int count, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Argument.OutOfRange(count >= 0, count, "count");
      Argument.OutOfRange(startIndex + count < array.Length, count, "count");
      Argument.NotNull(match, "match");
      return Array.FindIndex(array, startIndex, count, match);
    }

    #endregion FindIndex

    public static T FindLast<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.FindLast(array, match);
    }

    #region FindLastIndex

    public static int FindLastIndex<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.FindLastIndex(array, match);
    }

    public static int FindLastIndex<T>(this T[] array, int startIndex, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Argument.OutOfRange(startIndex < array.Length, startIndex, "startIndex");
      Argument.NotNull(match, "match");
      return Array.FindLastIndex(array, startIndex, match);
    }

    public static int FindLastIndex<T>(this T[] array, int startIndex, int count, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Argument.OutOfRange(count >= 0, count, "count");
      Argument.OutOfRange(startIndex + count < array.Length, count, "count");
      Argument.NotNull(match, "match");
      return Array.FindLastIndex(array, startIndex, count, match);
    }

    #endregion FindLastIndex

    #region IndexOf

    public static int IndexOf<T>(this T[] array, T value) {
      Argument.NotNull(array, "array");
      return Array.IndexOf(array, value);
    }

    public static int IndexOf<T>(this T[] array, T value, int startIndex) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      return Array.IndexOf(array, value, startIndex);
    }

    public static int IndexOf<T>(this T[] array, T value, int startIndex, int count) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Argument.OutOfRange(count >= 0, count, "count");
      Argument.OutOfRange(startIndex + count < array.Length, count, "count");
      return Array.IndexOf(array, value, startIndex, count);
    }

    #endregion IndexOf

    #region LastIndexOf

    public static int LastIndexOf<T>(this T[] array, T value) {
      Argument.NotNull(array, "array");
      return Array.LastIndexOf(array, value);
    }

    public static int LastIndexOf<T>(this T[] array, T value, int startIndex) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      return Array.LastIndexOf(array, value, startIndex);
    }

    public static int LastIndexOf<T>(this T[] array, T value, int startIndex, int count) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Argument.OutOfRange(count >= 0, count, "count");
      Argument.OutOfRange(startIndex + count < array.Length, count, "count");
      return Array.LastIndexOf(array, value, startIndex, count);
    }

    #endregion LastIndexOf

    #region Sort

    public static void Sort<T>(this T[] array) {
      Argument.NotNull(array, "array");
      Array.Sort(array);
    }

    public static void Sort<T>(this T[] array, Comparison<T> comparison) {
      Argument.NotNull(array, "array");
      Argument.NotNull(comparison, "comparison");
      Array.Sort(array, comparison);
    }

    public static void Sort<T>(this T[] array, IComparer<T> comparer) {
      Argument.NotNull(array, "array");
      Array.Sort(array, comparer);
    }

    public static void Sort<T>(this T[] array, int index, int length) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(index >= 0, index, "index");
      Argument.OutOfRange(length >= 0, length, "length");
      Argument.OutOfRange(length <= array.Length, length, "count");
      Array.Sort(array, index, length);
    }

    public static void Sort<T>(this T[] array, int index, int length, IComparer<T> comparer) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(index >= 0, index, "index");
      Argument.OutOfRange(length >= 0, length, "length");
      Argument.OutOfRange(length <= array.Length, length, "count");
      Array.Sort(array, index, length, comparer);
    }

    #endregion Sort

    public static bool TrueForAll<T>(this T[] array, Predicate<T> match) {
      Argument.NotNull(array, "array");
      Argument.NotNull(match, "match");
      return Array.TrueForAll(array, match);
    }
  }
}
